<?php
/**
 * WB_PersistentStoreManager class
 * Objects stored through this manager must
 * be of class (or derived from) WB_PersistentObjClass.
 * 
 * Local Store + Global Store
 * 
 * Use Cases:
 * ==========
 * u1: only LOCAL store
 * u2: only GLOBAL store
 * u3: LOCAL and GLOBAL store
 * 
 * - retrieve
 * -- FOUND LOCAL  | EXPIRED?
 * -- !FOUND LOCAL
 * - retrieve fresh
 * -- OK
 * -- !OK   | ?keep expired?
 * - retrieve from GLOBAL
 * -- FOUND
 * -- !FOUND 
 * 
 * @author Jean-Lou Dupont
 * @version @@package-version@@
 * @id $Id: PersistentStoreManager.php 157 2008-03-16 01:06:39Z jldupont $
 */
/*
 * RETRIEVAL PROCESS:
 * ==================
 *  1) LOCAL store present
 *     a) retrieve FOUND
 *     b) retrieve FOUND but EXPIRED
 *     c) retrieve !FOUND
 *     
 *  2) LOCAL store !present
 *     a) proceed to GLOBAL
 *     
 *  3) GLOBAL store present
 *     a) retrieve FOUND
 *     b) retrieve !FOUND + LOCAL copy !FOUND
 *     c) retrieve !FOUND + LOCAL copy FOUND but EXPIRED
 *     d) retrieve !FOUND + LOCAL copy FOUND and !EXPIRED
 * 
 * COMMIT PROCESS:
 * ===============
 *  1) LOCAL store present
 *     a) store OK
 *     b) store !OK
 *     
 *  2) LOCAL store !present
 *     a) proceed to GLOBAL
 *     
 *  3) GLOBAL store present
 *     a) store OK
 *     b) store !OK + LOCAL copy !OK
 *     c) store !OK + LOCAL copy OK
 * 
 */
 
 //{{include
  // must surely be loaded already!
 #require_once 'web-bloks/BaseClass/BaseClass.php'; 
 #require_once 'web-bloks/Singleton/Singleton.php'; 
 //include}}
 
 class WB_PersistentStoreManager
 	extends WB_BaseClass
	implements WB_Singleton_IF
 {
	/**
	 * Related to the 'store' operation
	 * Stores an object through the normal process
	 */
	const NORMAL_STORE = 0;
 	
	/**
	 * Related to the 'store' operation
	 * Store only in 'LOCAL' type store
	 */
	const LOCAL_ONLY  = 1;

	/**
	 * Related to the 'store' operation
	 * Store only in 'GLOBAL' type store
	 */
	const GLOBAL_ONLY = 2;
	
	/**
	 * Related to the 'restore' operation
	 * Restores an object through the normal process
	 */
	const NORMAL_RESTORE = 0;
	
	/**
	 * Reference to storage
	 * of LOCAL type
	 * @private
	 */
	var $_localStore = null;
	
	/**
	 * Reference to storage
	 * of GLOBAL type
	 * @private
	 */
	var $_globalStore = null;

	// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
	// ERROR codes
	// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%	
	const CREATION_FAILED = 0;
	const CREATION_FAILED_IDENTIFIER_EXISTS = 1;
	const CREATION_SUCCESS = 2;

	// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
	// Constructor
	// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%	
	
 	/**
 	 * Constructor
 	 */
	public function __construct( $params = null )
	{
		parent::__construct( $params );
	}
	// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
	// WB_BaseClass
	// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%	
	/**
	 * @see WB_BaseClass::getClassPath
	 */
	public function getClassPath()
	{
		return 'web-bloks/PersistentStoreManager';
	}
	/**
	 * @see WB_BaseClass::getClassVersion
	 * @return 
	 */
	public function getClassVersion()
	{
		return '$Id: PersistentStoreManager.php 157 2008-03-16 01:06:39Z jldupont $';
	}
	// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%		
	// WB_Singleton_IF Interface
	// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%				
	/**
	 * @private
	 */
	static $instance = null;

	/**
	 * @see WB_Singleton_IF
	 */
	public static function getSingleton( $params = null )	
	{ 
		/**
		 * @see WB_BaseClass::_handleSingleton
		 */
		return self::_handleSingleton( self::$instance, 'WB_PersistentStoreManager', $params );
	}
	// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%		
	// Management Interface
	// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%				
	/**
	 * Registers a $storage object
	 * 
	 * @return $this
	 * @param $storage Object of WB_PersistentStoreClass
	 */
	public function registerStorage( &$storage )
	{
		assert( $storage instanceof WB_PersistentStoreClass );
		
		$type = $storage->getType();
		/*
		 * Only one each type of storage is permitted
		 */
		switch( $type )
		{
			case WB_PersistentStoreClass::TYPE_LOCAL:
				if ( !is_null( $this->_localStore ) )
					$this->throwException( __METHOD__.':local storage already registered' );
				else
					$this->_localStore = $storage;
				break;
							
			case WB_PersistentStoreClass::TYPE_GLOBAL:
				if ( !is_null( $this->_globalStore ) )
					$this->throwException( __METHOD__.':global storage already registered' );
				else
					$this->_globalStore = $storage;
				break;
			
			default:
				$this->throwException( 'invalid storage type' );
		}
			
		return $this;				
	}
	// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%		
	// PUBLIC Interface
	// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%	
	/**
	 * Create the object in the store
	 * 
	 * @return code
	 * @param $obj of class WB_PersistentObjClass
	 */
	public function create( &$obj, $to_where = null )
	{
		$code = false;
		
		switch( $to_where )
		{
			case self::LOCAL_ONLY:
				$code = $this->createLocal( $obj );
				break;
			case self::GLOBAL_ONLY:
				$code = $this->createGlobal( $obj );
				break;
			case self::NORMAL_STORE:
				$code = $this->createNormal( $obj );
				break;
			default:
				$this->throwException( __METHOD__.':invalid from_where parameter' );			
		}
		return $code;
	}
				
	/**
	 * Stores $obj
	 * 
	 * @return $this
	 * @param $obj Object
	 * @param $to_where Store only to one location
	 * @throws WB_PersistentStoreManager_Exception
	 */
	public function store( &$obj, $to_where = self::NORMAL_STORE )
	{
		$code = false;
		
		switch( $to_where )
		{
			case self::LOCAL_ONLY:
				$code = $this->storeLocal( $obj );
				break;
			case self::GLOBAL_ONLY:
				$code = $this->storeGlobal( $obj );
				break;
			case self::NORMAL_STORE:
				$code = $this->storeNormal( $obj );
				break;
			default:
				$this->throwException( __METHOD__.':invalid from_where parameter' );			
		}
		return $code;
	}
	/**
	 * Restores an object from the persistent store
	 * 1) If forced to restore from GLOBAL_ONLY,
	 *    then also must update the LOCAL store
	 * 2) If forced to restore from LOCAL_ONLY,
	 *    then 
	 * 3) The normal process is to:
	 * 	  a) check the LOCAL store first
	 *    b) If the LOCAL store contains a valid copy 
	 *       (i.e. not expired) then restore
	 *    c) If the LOCAL store contains an expired copy
	 *       then restore from GLOBAL
	 *    d) If GLOBAL store can not be reached or
	 *       object not found THEN return 
	 *       d1) return LOCAL copy is available
	 *       d2) else return 
	 * 
	 * @return $obj Object
	 * @param $identifier WB_Identifier
	 * @param $from_where Whether to force restore from a location
	 * @throws WB_PersistentStoreManager_Exception
	 */
	public function restore( &$identifier, $from_where = self::NORMAL_RESTORE )
	{
		$obj = null;
		
		switch( $from_where )
		{
			case self::LOCAL_ONLY:
				$obj = $this->getFromLocal( $identifier );			
				break;
			case self::GLOBAL_ONLY:
				$obj = $this->getFromGlobal( $identifier );						
				break;
			case self::NORMAL_RESTORE:
				$obj = $this->getNormal( $identifier );
				break;
			default:
				$this->throwException( __METHOD__.':invalid from_where parameter' );
		}
		return $obj;
	}
	// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%		
	// Internal methods
	// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%				
						// %%%%%%%%%%% //		
						// RESTORATION
						// %%%%%%%%%%% //

	/**
	 * isLocalStoreValid
	 * Returns the status of the provisioned LOCAL store
	 * 
	 * @return boolean
	 */
	protected function isLocalStoreValid()
	{
		return ( !is_null( $this->_localStore ) );
	}
	/**
	 * isGlobalStoreValid
	 * Returns the status of the provisioned GLOBAL store
	 * 
	 * @return boolean
	 */
	protected function isGlobalStoreValid( )
	{
		return ( !is_null( $this->_globalStore ) );
	}
	/**
	 * Restores an object from the LOCAL store
	 * 
	 * @return $obj mixed
	 * @param $identifier WB_Identifier
	 * @throws WB_PersistentStoreManager_Exception
	 */	
	protected function getFromLocal( &$identifier, $suppress_exception = false )
	{
		$obj = false;
		
		try
		{
			$obj = $this->_localStore->restore( $identifier );
		}
		catch( Exception $e )
		{
			if ( !$suppress_exception )
				$this->throwFromException( $e );
		}
		return $obj;
	}	
	/**
	 * Restores an object from the GLOBAL store
	 * 
	 * @return $obj mixed
	 * @param $identifier WB_Identifier
	 * @throws WB_PersistentStoreManager_Exception
	 */	
	protected function getFromGlobal( &$identifier, $suppress_exception = false )
	{
		$obj = false;
		
		try
		{
			$obj = $this->_globalStore->restore( $identifier );
		}
		catch( Exception $e )
		{
			if ( !$suppress_exception )
				$this->throwFromException( $e );
		}
		return $obj;
	}	
	/**
	 * Normal restoration process
	 * 
	 * @return $obj Object
	 * @param $identifier WB_Identifier
	 * @throws WB_PersistentStoreManager_Exception
	 */
	protected function getNormal( &$identifier )
	{
		$lcode = null;
		$lobj = null;
		$gobj = null;
		$expired = null; // we do not know yet
		
		// we have a valid local store
		if ( $this->isValidLocalStore() )	
			$lobj = $this->getFromLocal( $identifier, true );

		if ( is_object( $lobj ))
			$expired = $lobj->isExpired();

		// do we need to get from GLOBAL ?
		if ( !$expired )		
			return $lobj;
			
		// it seems we have to go GLOBAL...
		if ( !$this->isGlobalStoreValid() )
			$this->throwException( __METHOD__.':global storage not registered or invalid' );
			
		$gobj = $this->getFromGlobal( $identifier, true );

		// if we were successful with fetching the object
		// from the GLOBAL store, update the local store... if possible
		if ( $gobj !== false )
			/*$lcode = */$this->storeLocal( $gobj, true );

		// Finally, if we have a valid object from the GLOBAL store,
		// return it!
		if ( $gobj !== false )
			return $gobj;
		
		// else, return the stale object... if any
		return $lobj;
	}

						// %%%%%%% //		
						// STORAGE
						// %%%%%%% //
	/**
	 * Stores the object in the LOCAL store
	 * Suppress any kind of error if we are asked to.
	 * 
	 * @return boolean Result code
	 * @param $obj WB_PersistentObjClass or derived
	 * @param $suppress_exception boolean[optional]
	 */
	protected function storeLocal( &$obj, $suppress_exception = false )
	{
		$code = null;
		
		try
		{
			$code = $this->_localStore->store( $obj );
		}
		catch( Exception $e )
		{
			if ( !$suppress_exception )
				$this->throwFromException( $e );
		}
		return $code;
	}
	/**
	 * Stores the object in the GLOBAL store
	 * Suppress any kind of error if we are asked to.
	 * 
	 * @return boolean Result code
	 * @param $obj WB_PersistentObjClass or derived
	 * @param $suppress_exception boolean[optional]
	 */
	protected function storeGlobal( &$obj, $suppress_exception = false )
	{
		$code = null;
		
		try
		{
			$code = $this->_globalStore->store( $obj );
		}
		catch( Exception $e )
		{
			if ( !$suppress_exception )
				$this->throwFromException( $e );
		}
		return $code;
	}
	/**
	 * Stores the object through the normal process
	 * Suppress any kind of error if we are asked to.
	 * 
	 * @return boolean Result code
	 * @param $obj WB_PersistentObjClass or derived
	 * @param $suppress_exception boolean[optional]
	 */
	protected function storeNormal( &$obj, $suppress_exception = false )
	{
		$lcode = null; 
		// we have a valid local store
		if ( $this->isValidLocalStore() )	
			$lcode = $this->storeLocal( $obj, true );
		
		// if we can't store locally and we are supposed
		// to be able to, then flag an error
		if ( $lcode === false )
			return false;

		$gcode = null;
		// next, store in GLOBAL store			
		if ( $this->isGlobalStoreValid() )		
			$gcode = $this->storeGlobal( $obj, true );
			
		return $gcode;			
	}
	
						// %%%%%%%% //		
						// CREATION
						// %%%%%%%% //
	/**
	 * Creates the object in the LOCAL store
	 * Suppress any kind of error if we are asked to.
	 * 
	 * @return boolean Result code
	 * @param $obj WB_PersistentObjClass or derived
	 * @param $suppress_exception boolean[optional]
	 */
	protected function createLocal( &$obj, $suppress_exception = false )
	{
		$code = null;
		
		try
		{
			$code = $this->_localStore->create( $obj );
		}
		catch( Exception $e )
		{
			if ( !$suppress_exception )
				$this->throwFromException( $e );
		}
		return $code;
	}
	/**
	 * Creates the object in the GLOBAL store
	 * Suppress any kind of error if we are asked to.
	 * 
	 * @return boolean Result code
	 * @param $obj WB_PersistentObjClass or derived
	 * @param $suppress_exception boolean[optional]
	 */
	protected function createGlobal( &$obj, $suppress_exception = false )
	{
		$code = null;
		
		try
		{
			$code = $this->_globalStore->create( $obj );
		}
		catch( Exception $e )
		{
			if ( !$suppress_exception )
				$this->throwFromException( $e );
		}
		return $code;
	}
	/**
	 * Creates the object through the normal process
	 * Suppress any kind of error if we are asked to.
	 * 
	 * @return boolean Result code
	 * @param $obj WB_PersistentObjClass or derived
	 * @param $suppress_exception boolean[optional]
	 */
	protected function createNormal( &$obj, $suppress_exception = false )
	{
		$lcode = null; 
		// we have a valid local store
		if ( $this->isValidLocalStore() )	
			$lcode = $this->createLocal( $obj, true );
		
		// if we can't create locally and we are supposed
		// to be able to, then flag an error
		if ( $lcode === false )
			return false;

		$gcode = null;
		// next, create in GLOBAL store			
		if ( $this->isGlobalStoreValid() )		
			$gcode = $this->createGlobal( $obj, true );
			
		return $gcode;			
	}

	
 } // end class declaration